﻿//-----------------------------------------------------------------------------
// Filename: SIPCallResolver.cs
//
// Description: Resolves user provided call strings into structures that can be oassed to other 
// applications to initiate SIP calls.
// 
// History:
// 10 Aug 2008	    Aaron Clauson	    Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2008 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Net;
using BlueFace.VoIP.Net.App;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using BlueFace.VoIP.SIPServerCores.StatefulProxy.DialPlanApps;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.SIPServerCores.StatefulProxy
{
    public class SIPCallResolver
    {
        private const string PSRCIP_HEADER_NAME = "P-src-ip: ";
        private const char CALLLEG_SIMULTANEOUS_SEPARATOR = '&';
        private const char CALLLEG_FOLLOWON_SEPARATOR = '|';
        public const char DESTINATION_PROVIDER_SEPARATOR = '@';
        private const string ANON_CALLERS = @"anonymous\.invalid|anonymous|anon";

        private static ILog logger = AppState.logger;

        public static string BetamaxServersPattern = null;                  // A regular expression to identify some Betamax servers so as the P-src-ip header can be added to try and circumvent their FUP.

        private static string m_anonymousUsername = SIPConstants.SIP_DEFAULT_USERNAME;
        private static string m_anonymousDomain = StatefulProxyCore.DEFAULT_DOMAIN;

        private string m_username;
        private List<SIPProvider> m_sipProviders;
        private GetSIPAccountBindingsDelegate GetSIPAccountBindings;
        private SIPDomains m_sipDomains;
        private SIPTransport m_sipTransport;

        public SIPCallResolver(SIPTransport sipTransport, string username, List<SIPProvider> sipProviders, GetSIPAccountBindingsDelegate getSIPAccountBindings, SIPDomains sipDomains)
        {
            m_sipTransport = sipTransport;
            m_username = username;
            m_sipProviders = sipProviders;
            GetSIPAccountBindings = getSIPAccountBindings;
            m_sipDomains = sipDomains;
        }

        /// <summary>
        /// Can't be used for local destinations!
        /// </summary>
        /// <param name="sipRequest"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        public SIPCallDescriptor GetCallStructForComamnd(SIPRequest sipRequest, string callLeg)
        {
            try
            {
                SIPCallDescriptor SIPCallDescriptor = SIPCallDescriptor.Empty;

                string[] splitLeg = SplitCallLegString(sipRequest.URI, callLeg);
                string providerDst = splitLeg[0];
                string providerName = splitLeg[1];

                    logger.Debug("Attempting to locate a provider for call leg: " + callLeg + ".");
                    bool providerFound = false;

                    foreach (SIPProvider provider in m_sipProviders)
                    {
                        if (providerName.ToUpper() == provider.ProviderName.ToUpper())
                        {
                            SIPURI dstURI = new SIPURI(providerDst, provider.ProviderServer, null);
                            string forwardURI = SIPDialPlan.SubstituteRequestVars(sipRequest, dstURI.ToString());
                            SIPFromHeader fromHeader = ParseFromHeaderOption(provider.ProviderFrom, sipRequest, provider.ProviderUsername, dstURI.Host);
                            string host = (provider.ProviderOutboundProxy != null && provider.ProviderOutboundProxy.Trim().Length > 0) ? provider.ProviderOutboundProxy : dstURI.Host;

                            SIPCallDescriptor = new SIPCallDescriptor(
                                provider.ProviderUsername,
                                provider.ProviderPassword,
                                forwardURI,
                                fromHeader.ToString(),
                                forwardURI,
                                host,
                                provider.CustomHeaders,
                                SIPCallDirection.Out);

                            providerFound = true;
                            break;
                        }
                    }

                    if (!providerFound)
                    {
                        // Treat as an anonymous SIP URI.
                        if (providerDst != null && providerName != null)
                        {
                            if (providerDst.Trim().ToLower().StartsWith("sip:"))
                            {
                                providerDst = providerDst.Trim().Substring(4);
                            }

                            SIPURI dstURI = new SIPURI(providerDst, providerName, null);
                            string forwardURI = SIPDialPlan.SubstituteRequestVars(sipRequest, dstURI.ToString());

                            // Copy the From header so the tag can be removed before adding to the forwarded request.
                            SIPFromHeader forwardedFromHeader = SIPFromHeader.ParseFromHeader(sipRequest.Header.From.ToString());
                            forwardedFromHeader.FromTag = null;


                            SIPCallDescriptor = new SIPCallDescriptor(
                                m_anonymousUsername,
                                null,
                                forwardURI,
                                forwardedFromHeader.ToString(), //"<sip:" + m_anonymousUsername + "@" + m_anonymousDomain + ">",
                                forwardURI,
                                dstURI.Host,
                                null,
                                SIPCallDirection.Out);
                        }
                        else
                        {
                            logger.Warn("A SIP Provider for " + callLeg + " was not found for in dial plan command, " + callLeg + ".");
                            //m_statefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "SIP Provider for " + callLeg + " not found, ignoring.", m_username));
                        }
                    }

                return SIPCallDescriptor;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetSingleCallLeg. " + excp.Message);
                return SIPCallDescriptor.Empty;
            }
        }

        /// <summary>
        /// Count the commas to determine if this is an old Switch command that only allows a single destination or whether it is a new Swtich command
        /// that allows mutliple destinations and uses SIP Provider entries. An old switch command will always have 2 or more commas the new one will
        /// only ever have a single comma.
        /// </summary>
        /// <returns></returns>
        public Queue<List<SIPCallDescriptor>> BuildCallList(SIPRequest sipRequest, string command)
        {
            try
            {
                if (command == null || command.Trim().Length == 0)
                {
                    throw new ArgumentException("The command data cannot be empty for BuildCallList.");
                }
                else
                {
                    Queue<List<SIPCallDescriptor>> priorityCallList = new Queue<List<SIPCallDescriptor>>();

                    if (command.Split(',').Length <= 2)
                    {
                        // Multiple forwards. Truncate off the ",true" trace option at the end if it's present.
                        string providersString = (command.IndexOf(',') == -1) ? command : command.Substring(0, command.IndexOf(','));
                        priorityCallList = BuildProviderCallList(sipRequest, providersString);
                    }
                    else
                    {
                        // Singled legged call.
                        SIPCallDescriptor SIPCallDescriptor = ParseAppData(command, sipRequest);
                        List<SIPCallDescriptor> callList = new List<SIPCallDescriptor>();
                        callList.Add(SIPCallDescriptor);
                        priorityCallList.Enqueue(callList);
                    }

                    return priorityCallList;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception BuildCallList. " + excp);
                throw excp;
            }
        }

        public Queue<List<SIPCallDescriptor>> BuildProviderCallList(SIPRequest sipRequest, string command)
        {
            try
            {
                Queue<List<SIPCallDescriptor>> callsQueue = new Queue<List<SIPCallDescriptor>>();

                string[] followonLegs = command.Split(CALLLEG_FOLLOWON_SEPARATOR);

                foreach (string followOnLeg in followonLegs)
                {
                    List<SIPCallDescriptor> switchCalls = new List<SIPCallDescriptor>();
                    string[] callLegs = followOnLeg.Split(CALLLEG_SIMULTANEOUS_SEPARATOR);

                    foreach (string callLeg in callLegs)
                    {
                        // Lookup provider in list and create a call command based on it.
                        if (callLeg != null && callLeg.Trim().Length > 0)
                        {
                            if (IsLocal(callLeg))
                            {
                                string[] splitLeg = SplitCallLegString(sipRequest.URI, callLeg);
                                string providerDst = splitLeg[0];
                                string providerName = splitLeg[1];

                                if (providerDst.Trim().ToLower().StartsWith("sip:"))
                                {
                                    providerDst = providerDst.Trim().Substring(4);
                                }

                                logger.Debug("Call leg is local domain looking up proxy registered contact for " + providerDst + " in " + callLeg + ".");
                                switchCalls.AddRange(GetCallListForLocalUser(providerDst, providerName, sipRequest.Header.From.ToString()));
                            }
                            else
                            {
                                SIPCallDescriptor SIPCallDescriptor = GetCallStructForComamnd(sipRequest, callLeg);
                                switchCalls.Add(SIPCallDescriptor);
                            }
                        }
                    }

                    callsQueue.Enqueue(switchCalls);
                }

                return callsQueue;
            }
            catch (Exception excp)
            {
                logger.Error("Exception BuildProviderCallList. " + excp.Message);
                throw excp;
            }
        }

        public List<SIPCallDescriptor> GetCallListForLocalUser(string callLeg, string from)
        {
            string[] splitLeg = callLeg.Split(DESTINATION_PROVIDER_SEPARATOR);
            string providerDst = splitLeg[0];
            string providerName = splitLeg[1];

            return GetCallListForLocalUser(providerDst, providerName, from);
        }

        /// <summary>
        /// Creates a list of calls based on the registered contacts for a user registration.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="domain"></param>
        /// <param name="from">The From header that will be set on the forwarded call leg.</param>
        /// <returns></returns>
        public List<SIPCallDescriptor> GetCallListForLocalUser(string user, string domain, string from)
        {
            List<SIPCallDescriptor> localUserSwitchCalls = new List<SIPCallDescriptor>();

            try
            {
                SIPParameterlessURI localURI = new SIPParameterlessURI(SIPSchemesEnum.sip, domain, user);
                //SIPRegistrarRecord registrarRecord = SIPRegistrations.Lookup(localURI);
                List<SIPAddressBinding> bindings = GetSIPAccountBindings(user, domain);

                if (bindings != null)
                {
                    logger.Debug(bindings.Count + " found for " + user + "@" + domain + " [" + m_sipDomains.GetDomain(domain) + "].");
                    //List<SIPAddressBinding> bindings = registrarRecord.GetBindings();

                    // Build list of registered contacts.
                    for (int index = 0; index < bindings.Count; index++)
                    {
                        SIPAddressBinding binding = bindings[index];
                        SIPURI contactURI = binding.MangledContactURI;
                        IPEndPoint contactEndPoint = contactURI.GetURIEndPoint();

                        if (contactEndPoint != null)
                        {
                            logger.Debug("Creating switch call for local user " + contactURI.ToString() + " at " + IPSocket.GetSocketString(contactEndPoint) + ".");
                            IPEndPoint host = contactEndPoint;

                            // If the binding has a proxy socket defined that's NOT a socket on this agent then the call should be sent to the proxy for forwarding to the user agent.
                            if (binding.ProxyEndPoint != null)
                            {
                                if (!m_sipTransport.IsLocalSIPSocket(binding.ProxyEndPoint.ToString(), binding.ProxyProtocol))
                                {
                                    host = binding.ProxyEndPoint;
                                }
                            }

                            SIPCallDescriptor switchCall = new SIPCallDescriptor(null, null, contactURI.ToString(), from, contactURI.ToString(), host.ToString(), null, SIPCallDirection.In);
                            localUserSwitchCalls.Add(switchCall);
                        }
                        else
                        {
                            logger.Debug("Could not resolve " + contactURI.ToString() + " for " + user + "@" + domain + ".");
                        }
                    }
                }
                else
                {
                    logger.Debug("No user found for " + localURI.ToString() + ".");
                }

                return localUserSwitchCalls;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetCallListForLocalUser. " + excp);
                return localUserSwitchCalls;
            }
        }

        public bool IsLocal(string provider)
        {
            if (provider == null || provider.Trim().Length == 0)
            {
                return false;
            }
            else if (provider.IndexOf(DESTINATION_PROVIDER_SEPARATOR) != -1)
            {
                return m_sipDomains.HasDomain(provider.Split(DESTINATION_PROVIDER_SEPARATOR)[1]);
            }
            else
            {
                return m_sipDomains.HasDomain(provider);
            }
        }

        private string[] SplitCallLegString(SIPURI destURI, string callLeg)
        {
            string providerDst = destURI.User;
            string providerName = callLeg;

            int indexOfDestSep = callLeg.IndexOf(DESTINATION_PROVIDER_SEPARATOR);
            if (indexOfDestSep != -1)
            {
                providerDst = callLeg.Substring(0, indexOfDestSep);
                providerName = callLeg.Substring(indexOfDestSep + 1);
            }

            return new string[] { providerDst, providerName };
        }

        /// <summary>
        /// The From header on forwarded calls can be customised. This method parses the dial plan option for
        /// the From header field or lack of it field and produces the From header string that will be used for
        /// forwarded calls.
        /// </summary>
        /// <param name="fromHeaderOption"></param>
        /// <returns></returns>
        private SIPFromHeader ParseFromHeaderOption(string fromHeaderOption, SIPRequest sipRequest, string username, string forwardURIHost)
        {
            SIPFromHeader fromHeader = null;

            if (fromHeaderOption != null && fromHeaderOption.Trim().Length > 0)
            {
                SIPValidationError fromParserError = SIPValidationError.None;
                SIPFromHeader dialplanFrom = SIPFromHeader.ParseFromHeader(fromHeaderOption, out fromParserError);
                if (fromParserError != SIPValidationError.None)
                {
                    throw new ApplicationException("A dial plan From header value could not be parsed " + fromParserError + ". Value=" + fromHeaderOption + ".");
                }
                else
                {
                    fromHeader = SIPFromHeader.ParseFromHeader(SIPDialPlan.SubstituteRequestVars(sipRequest, fromHeaderOption));
                }
            }
            else if (Regex.Match(username, ANON_CALLERS).Success)
            {
                fromHeader = SIPFromHeader.ParseFromHeader("sip:" + username + "@" + sipRequest.Header.From.FromURI.Host);
            }
            else
            {
                fromHeader = SIPFromHeader.ParseFromHeader("sip:" + username + "@" + forwardURIHost);
            }

            return fromHeader;
        }

        /// <summary>
        /// Builds the call list based on the original dial plan SwitchCall format. This will result in only a single call leg with a single forward
        /// destination. The format of the command is:
        ///
        /// exten = number,priority,Switch(username,password,new number[,From[,SendTo[,Trace]]]) 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private SIPCallDescriptor ParseAppData(string data, SIPRequest sipRequest)
        {
            try
            {
                string username = null;
                string password = null;
                string sendToSocket = null;
                bool traceReqd = false;
                string forwardURIStr = null;
                string fromHeaderStr = null;
                SIPURI forwardURI = null;

                string[] dataFields = data.Split(new char[] { ',' });

                username = dataFields[0].Trim().Trim(new char[] { '"', '\'' });
                password = dataFields[1].Trim().Trim(new char[] { '"', '\'' });
                forwardURIStr = dataFields[2].Trim().Trim(new char[] { '"', '\'' });

                if (dataFields.Length > 3 && dataFields[3] != null)
                {
                    fromHeaderStr = dataFields[3].Trim();
                }

                if (dataFields.Length > 4 && dataFields[4] != null)
                {
                    sendToSocket = dataFields[4].Trim().Trim(new char[] { '"', '\'' });
                }

                if (dataFields.Length > 5 && dataFields[5] != null)
                {
                    Boolean.TryParse(dataFields[5].Trim(), out traceReqd);
                }

                if (!forwardURIStr.StartsWith("sip:"))
                {
                    forwardURIStr = "sip:" + forwardURIStr;
                }

                forwardURI = SIPURI.ParseSIPURI(SIPDialPlan.SubstituteRequestVars(sipRequest, forwardURIStr));

                SIPFromHeader callFromHeader = ParseFromHeaderOption(fromHeaderStr, sipRequest, username, forwardURI.Host);
                string socket = (sendToSocket != null && sendToSocket.Trim().Length > 0) ? sendToSocket : forwardURI.Host;

                // Add special header for Betamax servers to see if it helps with the FUP.
                string customHeaders = null;
                if (BetamaxServersPattern != null && Regex.Match(forwardURI.Host, BetamaxServersPattern, RegexOptions.IgnoreCase).Success)
                {
                    customHeaders = PSRCIP_HEADER_NAME + sipRequest.ReceivedFrom.Address.ToString();
                }

                SIPCallDescriptor switchCallStruct = new SIPCallDescriptor(username, password, forwardURI.ToString(), callFromHeader.ToString(), forwardURI.ToString(), socket, customHeaders, SIPCallDirection.Out);

                return switchCallStruct;
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPCallDescriptor ParseAppData. " + excp.Message);
                return SIPCallDescriptor.Empty;
            }
        }

        #region Unit testing.

        #if UNITTEST

		[TestFixture]
		public class SIPCallResolverUnitTest
		{			
			[TestFixtureSetUp]
			public void Init()
			{ }

            [TestFixtureTearDown]
            public void Dispose()
            { }

			[Test]
			public void SampleTest()
			{
				Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);
				
				Assert.IsTrue(true, "True was false.");

				Console.WriteLine("---------------------------------"); 
			}

            [Test]
            public void SingleProviderLegUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "blueface", "test", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "blueface");

                Assert.IsNotNull(callQueue, "The call list should have contained a call.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should have contained one leg.");

                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 1, "The first call leg should have had one switch call.");
                Assert.IsTrue(firstLeg[0].Username == "test", "The username for the first call leg was not correct.");
                Assert.IsTrue(firstLeg[0].Uri.ToString() == "sip:1234@sip.blueface.ie", "The destination URI for the first call leg was not correct.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void SingleProviderWithDstLegUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "blueface", "test", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "303@blueface");

                Assert.IsNotNull(callQueue, "The call list should have contained a call.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should have contained one leg.");

                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 1, "The first call leg should have had one switch call.");
                Assert.IsTrue(firstLeg[0].Username == "test", "The username for the first call leg was not correct.");
                Assert.IsTrue(firstLeg[0].Uri.ToString() == "sip:303@sip.blueface.ie", "The destination URI for the first call leg was not correct.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void NoMatchingProviderUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "blueface", "test", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "303@noprovider");

                Assert.IsNotNull(callQueue, "The call list should be returned.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should not have contained one leg.");
                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 1, "The first call leg should have had one switch call.");
                Assert.IsTrue(firstLeg[0].Username == SIPCallResolver.m_anonymousUsername, "The username for the first call leg was not correct.");
                Assert.IsTrue(firstLeg[0].Uri.ToString() == "sip:303@noprovider", "The destination URI for the first call leg was not correct.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void ParseTraceOptionFromProviderUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "blueface", "test", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "blueface, true");

                Assert.IsNotNull(callQueue, "The call list should have contained a call.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should have contained one leg.");

                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 1, "The first call leg should have had one switch call.");
                Assert.IsTrue(firstLeg[0].Username == "test", "The username for the first call leg was not correct.");
                Assert.IsTrue(firstLeg[0].Uri.ToString() == "sip:1234@sip.blueface.ie", "The destination URI for the first call leg was not correct.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void IsLocalProviderUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPDomains sipDomains = new SIPDomains();
                sipDomains.AddDomain("local");
                SIPCallResolver callResolver = new SIPCallResolver("test", null, (user, domain) => { return null; }, sipDomains);
                bool isLocal = callResolver.IsLocal("aaron@local");

                Assert.IsTrue(isLocal, "The call leg should have been recognised as local.");

                Console.WriteLine("---------------------------------"); 
            }

            [Test]
            public void LookupSIPAccountUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPDomains sipDomains = new SIPDomains();
                sipDomains.AddDomain("local");
                SIPCallResolver callResolver = new SIPCallResolver("test", null, (user, domain) => { Console.WriteLine("lookup: " + user + "@" + domain + "."); return null; }, sipDomains);

                List<SIPCallDescriptor> callList = callResolver.GetCallListForLocalUser("aaron@local", null);

                Assert.IsTrue(callList.Count == 0, "No local contacts should have been returned.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void MultipleForwardsUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "provider1", "user", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                SIPProvider provider2 = new SIPProvider("test", "provider2", "user", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);
                providers.Add(provider2);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "provider1&provider2");

                Assert.IsNotNull(callQueue, "The call list should have contained a call.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should have contained one leg.");

                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 2, "The first call leg should have had two switch calls.");

                Console.WriteLine("---------------------------------");
            }

            [Test]
            public void MultipleForwardsWithLocalUnitTest()
            {
                Console.WriteLine("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name);

                SIPRequest inviteRequest = new SIPRequest(SIPMethodsEnum.INVITE, SIPURI.ParseSIPURI("sip:1234@localhost"));
                SIPHeader inviteHeader = new SIPHeader(SIPFromHeader.ParseFromHeader("<sip:joe@localhost>"), SIPToHeader.ParseToHeader("<sip:jane@localhost>"), 23, CallProperties.CreateNewCallId());
                SIPViaHeader viaHeader = new SIPViaHeader("127.0.0.1", 5060, CallProperties.CreateBranchId());
                inviteHeader.Via.PushViaHeader(viaHeader);
                inviteRequest.Header = inviteHeader;

                List<SIPProvider> providers = new List<SIPProvider>();
                SIPProvider provider = new SIPProvider("test", "provider1", "user", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                SIPProvider provider2 = new SIPProvider("test", "provider2", "user", "password", "sip.blueface.ie", null, null, null, null, 3600, null, null, null, false, false);
                providers.Add(provider);
                providers.Add(provider2);

                SIPCallResolver callResolver = new SIPCallResolver("test", providers, (user, domain) => { return null; }, new SIPDomains());
                Queue<List<SIPCallDescriptor>> callQueue = callResolver.BuildCallList(inviteRequest, "local&1234@provider2");

                Assert.IsNotNull(callQueue, "The call list should have contained a call.");
                Assert.IsTrue(callQueue.Count == 1, "The call queue list should have contained one leg.");

                List<SIPCallDescriptor> firstLeg = callQueue.Dequeue();

                Assert.IsNotNull(firstLeg, "The first call leg should exist.");
                Assert.IsTrue(firstLeg.Count == 2, "The first call leg should have had two switch calls.");

                Console.WriteLine("First destination uri=" + firstLeg[0].Uri.ToString());
                Console.WriteLine("Second destination uri=" + firstLeg[1].Uri.ToString());

                Console.WriteLine("---------------------------------");
            }
        }

        #endif

        #endregion
    }
}
